<?php
/**
 * Fonctions principales du jeu
 */


/**
 * Retourne les cartes qu'il faut selon les règles d'une bataille classique
 *
 * @param $add : la carte jouée
 * @param $board : le plateau de jeu
 *
 * @return int : la position de la carte de droite
 * 	       null : si la carte n'existe pas
 */
function execCardBattle($add, $game, $cardCollection, $playerId, &$points = 0 ){

	//Récupération des positions à combattre
	$boardLenght = count($game['board']);
	$boardWidth = $boardHeight = sqrt($boardLenght);

	$actualPosition = $add['cell'];

	$actualCard = $cardCollection[$add['card']];
	$actualHorizontalPosition = $actualPosition % $boardWidth;
	$actualVerticalPosition = ($actualPosition - $actualHorizontalPosition) / $boardWidth;

	// La carte attaque dans toutes les directions
	if($actualVerticalPosition != ($boardHeight - 1))
		$game = attackCard($actualCard, $actualHorizontalPosition, $actualVerticalPosition, $boardHeight, $game, $cardCollection, $playerId, $points, 'bottom');

	if($actualVerticalPosition != 0)
		$game = attackCard($actualCard, $actualHorizontalPosition, $actualVerticalPosition, $boardHeight, $game, $cardCollection, $playerId, $points, 'top');

	if($actualHorizontalPosition != ($boardWidth - 1))
		$game = attackCard($actualCard, $actualHorizontalPosition, $actualVerticalPosition, $boardHeight, $game, $cardCollection, $playerId, $points, 'right');

	if($actualHorizontalPosition != ($boardWidth - 1) && $actualVerticalPosition != ($boardHeight - 1))
		$game = attackCard($actualCard, $actualHorizontalPosition, $actualVerticalPosition, $boardHeight, $game, $cardCollection, $playerId, $points, 'right-bottom');

	if($actualHorizontalPosition != ($boardWidth - 1) && $actualVerticalPosition != 0)
		$game = attackCard($actualCard, $actualHorizontalPosition, $actualVerticalPosition, $boardHeight, $game, $cardCollection, $playerId, $points, 'right-top');

	if($actualHorizontalPosition != 0)
		$game = attackCard($actualCard, $actualHorizontalPosition, $actualVerticalPosition, $boardHeight, $game, $cardCollection, $playerId, $points, 'left');

	if($actualHorizontalPosition != 0 && $actualVerticalPosition != ($boardHeight - 1))
		$game = attackCard($actualCard, $actualHorizontalPosition, $actualVerticalPosition, $boardHeight, $game, $cardCollection, $playerId, $points, 'left-bottom');

	if($actualHorizontalPosition != 0 && $actualVerticalPosition != 0)
		$game = attackCard($actualCard, $actualHorizontalPosition, $actualVerticalPosition, $boardHeight, $game, $cardCollection, $playerId, $points, 'left-top');

	return $game;
}


function key_compare_func($a, $b)
{
    if ($a == $b) {
        return 0;
    }
    return ($a > $b)? 1:-1;
}

/*
 * Attaque la carte qui se trouve dans la direction demandée
 *
 * Retourne le jeu mis à jour
 *
 * @return $game mis à jour
 */
function attackCard($actualCard, $actualHorizontalPosition, $actualVerticalPosition, $boardHeight, $game, $cardCollection, $playerId, &$points, $direction='right'){
	switch($direction){
		case 'right':
			$cardAttackedPosition = ($actualHorizontalPosition + 1) + $actualVerticalPosition * $boardHeight;
			if(!isset($game['board'][$cardAttackedPosition]) || $game['board'][$cardAttackedPosition] == 'default'){
				return $game;
			}
			$cardAttacked = $cardCollection[$game['board'][$cardAttackedPosition][0]];
			$valueOfCardAttacked = $cardAttacked[7];
			$valueOfChallengerCard = $actualCard[3];
			break;
		case 'left':
			$cardAttackedPosition = ($actualHorizontalPosition - 1) + $actualVerticalPosition * $boardHeight;
			if(!isset($game['board'][$cardAttackedPosition]) || $game['board'][$cardAttackedPosition] == 'default'){
				return $game;
			}
			$cardAttacked = $cardCollection[$game['board'][$cardAttackedPosition][0]];
			$valueOfCardAttacked = $cardAttacked[3];
			$valueOfChallengerCard = $actualCard[7];
			break;
		case 'top':
			$cardAttackedPosition = $actualHorizontalPosition + ($actualVerticalPosition - 1) * $boardHeight;
			if(!isset($game['board'][$cardAttackedPosition]) || $game['board'][$cardAttackedPosition] == 'default'){
				return $game;
			}
			$cardAttacked = $cardCollection[$game['board'][$cardAttackedPosition][0]];
			$valueOfCardAttacked = $cardAttacked[5];
			$valueOfChallengerCard = $actualCard[1];
			break;
		case 'bottom':
			$cardAttackedPosition = $actualHorizontalPosition + ($actualVerticalPosition + 1) * $boardHeight;
			if(!isset($game['board'][$cardAttackedPosition]) || $game['board'][$cardAttackedPosition] == 'default'){
				return $game;
			}
			$cardAttacked = $cardCollection[$game['board'][$cardAttackedPosition][0]];
			$valueOfCardAttacked = $cardAttacked[1];
			$valueOfChallengerCard = $actualCard[5];
			break;
		case 'left-bottom':
			$cardAttackedPosition = ($actualHorizontalPosition - 1) + ($actualVerticalPosition + 1) * $boardHeight ;
			if(!isset($game['board'][$cardAttackedPosition]) || $game['board'][$cardAttackedPosition] == 'default'){
				return $game;
			}
			$cardAttacked = $cardCollection[$game['board'][$cardAttackedPosition][0]];
			$valueOfCardAttacked = $cardAttacked[2];
			$valueOfChallengerCard = $actualCard[6];
			break;
		case 'right-bottom':
			$cardAttackedPosition = ($actualHorizontalPosition + 1) + ($actualVerticalPosition + 1) * $boardHeight;
			if(!isset($game['board'][$cardAttackedPosition]) || $game['board'][$cardAttackedPosition] == 'default'){
				return $game;
			}
			$cardAttacked = $cardCollection[$game['board'][$cardAttackedPosition][0]];
			$valueOfCardAttacked = $cardAttacked[0];
			$valueOfChallengerCard = $actualCard[4];
			break;
		case 'left-top':
			$cardAttackedPosition = ($actualHorizontalPosition - 1) + ($actualVerticalPosition - 1) * $boardHeight;
			if(!isset($game['board'][$cardAttackedPosition]) || $game['board'][$cardAttackedPosition] == 'default'){
				return $game;
			}
			$cardAttacked = $cardCollection[$game['board'][$cardAttackedPosition][0]];
			$valueOfCardAttacked = $cardAttacked[4];
			$valueOfChallengerCard = $actualCard[0];
			break;
		case 'right-top':
			$cardAttackedPosition = ($actualHorizontalPosition + 1) + ($actualVerticalPosition - 1) * $boardHeight;
			if(!isset($game['board'][$cardAttackedPosition]) || $game['board'][$cardAttackedPosition] == 'default'){
				return $game;
			}
			$cardAttacked = $cardCollection[$game['board'][$cardAttackedPosition][0]];
			$valueOfCardAttacked = $cardAttacked[6];
			$valueOfChallengerCard = $actualCard[2];
			break;
		default:
			return $game;
		break;
	}
	if(is_numeric($valueOfCardAttacked) && is_numeric($valueOfChallengerCard) && $valueOfCardAttacked < $valueOfChallengerCard){
		$game['board'][$cardAttackedPosition][1] = $playerId;
		$points ++;
	}

	return $game;
}


/*
 * Passe au joueur suivant
 *
 * @return $game mis à jour
 */
function changeNextPlayer($game){
	$game["playerTurn"] = getNextPlayer($game);
	return $game;
}

/*
 * Indique l'id du joueur suivant
 *
 * @return $game mis à jour
 */
function getNextPlayer($game){
	$playerKeys = array_keys($game['players']);
	$playerPosition = array_search($game["playerTurn"], $playerKeys);
	$nextPlayerPosition = $playerPosition + 1;
	
	if(!isset($playerKeys[$nextPlayerPosition])){
		return $playerKeys[0];
	}

	return $playerKeys[$nextPlayerPosition];
}

/*
 * Indique l'id du joueur précédent
 *
 * @return $game mis à jour
 */
function getPreviousPlayer($game){
	$playerKeys = array_keys($game['players']);
	$playerPosition = array_search($game["playerTurn"], $playerKeys);
	$previousPlayerPosition = $playerPosition - 1;
	
	if(!isset($playerKeys[$previousPlayerPosition])){
		return end($playerKeys);
	}

	return $playerKeys[$previousPlayerPosition];
}

/*
 * Indique l'id du joueur human précédent
 *
 * @return $game mis à jour
 */
function getLastHumanPlayer($game){
	$playerKeys = array_keys($game['players']);
	$playerPosition = array_search($game["playerTurn"], $playerKeys);
	$previousPlayerPosition = $playerPosition - 1;
	$i =0;
	while($i < count($playerKeys)){
		if($previousPlayerPosition < 0){
			$previousPlayerPosition = count($playerKeys) - 1;
		}
		if($game["players"][$playerKeys[$previousPlayerPosition]]['type'] == 'human'){
			return $playerKeys[$previousPlayerPosition];
		}
		$previousPlayerPosition--;
		$i++;
	}		
	return null;
}

/*
 * Detecte si la partie est fini
 *
 * Retourne le jeu mis à jour
 *
 *   0:en cours de jeu,
 *   1:win,
 *   2:loose,
 *   3:equality
 */
function detectEndOfGame($gameId, $game, $playerId){
	if(!in_array('default', $game['board'])){
		$game['endOfGame'] = 2;

		//Compte les points
		$points = array();
		$maxPoints = 0;
		$colorWinner = '';
		foreach($game['board'] as $card){
			if(!isset($points[$card[1]])){
				$points[$card[1]] = 0;
			}
			$points[$card[1]]++;
			if($maxPoints < $points[$card[1]]){
				$maxPoints = $points[$card[1]];
				$colorWinner = $card[1];
			}
		}
		$nbOfWinners=0;
		foreach($points as $playerPoints){
			if($maxPoints == $playerPoints){
				$nbOfWinners ++;
			}
		}
		if($nbOfWinners == 1){
			$game['endOfGame'] = 1;
			//Récupération du nom du joueur
			foreach($game['players'] as $playerId => $playerInfo){
				if($playerInfo['color'] == $colorWinner){
					$game['winner'] = $playerId;
				}
			}
		}else{
			$game['endOfGame'] = 3;
		}
	}


	
	return $game;
}

/*
 * Genere une main de cartes aléatoire
 *
 * @param $nbCards : le nombre de carte à génerer
 * @param $cardCollection : un tableau de cartes
 * @return $deck à jour
 *
 */
function getRandomDeck($nbCards, $cardCollection){
	$deck = array();
	for($i = 0; $i < $nbCards ; $i++){
		$deck[] = 'carte_' .  abs(rand(0, count($cardCollection) -1));
	}
	sort($deck);
	return $deck;
}

/*
 * Retire une carte d'un tableau de cartes
 *
 * @param $deck : le tableau de cartes
 * @param $card : la carte à retirer
 * @return $deck à jour
 *
 */
function takeCard($deck, $card){
	foreach($deck as $keyCardTmp => $cardTmp){
		if($cardTmp == $card){
			array_splice($deck, $keyCardTmp, 1 );
			break;
		}
	}
	sort($deck);
	return $deck;
}

/*
 * Vérifie si un id de joueur est présent dans la partie
 *
 * @param $game : la partie en cours
 * @param $playerId : l'id du joueur
 * @return true ou false
 *
 */
function isInGame($game, $playerId){
	foreach($game["players"] as $playerIdTmp => $playerInfosTmp){
		if($playerIdTmp == $playerId){
			return true;
		}
	}
	return false;
}

/*
 * Vérifie que le joueur possède toutes les cartes demandés
 *
 * @param $deck : un tableau de cartes à vérifier
 * @param $playerDeck :le jeu du joueur
 * @return true ou false
 *
 */
function checkPlayerCards($deck, $playerDeck){
	// Décompte du nombre de carte par classe
	$deckCount = array_count_values($deck);
	$playerDeckCount = array_count_values($playerDeck);
	foreach($deckCount as $cardId => $cardCount){
		if(!isset($playerDeckCount[$cardId]) || $playerDeckCount[$cardId] < $cardCount){
			return false;
		}
	}
	return true;
}

/*
 * Met en quarantaine les cartes du joueur pour la partie
 *
 * @param $deck : un tableau de cartes à mettre en quarantaine
 * @param $playerDeck :le jeu du joueur
 * @return $playerDeck mis à jour
 *
 */
function blockPlayerCards($deck, $playerDeck){
	// Décompte du nombre de carte par classe
	foreach($deck as $cardId => $card){
		foreach($playerDeck as $playerKeyCard => $playerCard){
			if($card == $playerCard){
				unset($playerDeck[$playerKeyCard]);
				break;
			}
		}
	}
	return $playerDeck;
}

/*
 * Libère les cartes des joueurs bloquées par la partie
 *
 * @param $game : la partie
 * @return /
 *
 */
function releasePlayerCards($game){
	$gameId = $game['id'];
	//Libération des cartes bloqués
	foreach($game['players'] as $playerIdTmp => $playerInfosTmp){
		$player = loadPlayer($playerIdTmp);
		if(!isset($player['blocked'][$gameId])){
			continue;
		}
		$player['deck'] = array_merge($player['deck'], $player['blocked'][$gameId]);
		unset($player['blocked'][$gameId]);
		sort($player['deck']);
		savePlayer($player);
	}
}

/*
 * Ajoute une carte au jeu du joueur
 *
 * @param $playerId : l'id du joueur
 * @param $card : le nom de la carte
 * @return /
 *
 */
function addCardToPlayer($playerId, $card){
	$player = loadPlayer($playerId);
	$player['deck'][] = $card;
	sort($player['deck']);
	savePlayer($player);
}

/*
 * Supprime une carte du jeu du joueur
 *
 * @param $playerId : l'id du joueur
 * @param $card : le nom de la carte
 * @return false en cas d'erreur
 *
 */
function takePlayerCard($playerId, $gameId, $card){
	$player = loadPlayer($playerId);
	if(!isset($player['blocked'][$gameId])){
		return false;
	}

	foreach($player['blocked'][$gameId] as $cardKey => $cardId){
		if($cardId == $card){
			unset($player['deck'][$cardKey]);
			sort($player['deck']);
			var_export($player);
			savePlayer($player);
			return true;
			break;
		}
	}
	return false;
}

/*
 * Renvoie un tableau contenant la liste des cartes à choisir après une victoire
 *
 * @param $game : la partie
 * @return array('joueur_id' => array('carte_1', 'carte_2'..)
 * 				...)
 *
 */
function getCardsToChoose($game){
	$cards = array();
	
	foreach($game['players'] as $playerIdTmp => $playerInfosTmp){
		if($playerIdTmp != $game['winner'])
			$cards[$playerIdTmp] = $playerInfosTmp['original'];
	}
	
	return $cards;
}
/*
 * Enregistrement de la partie
 *
 * @param $game : la partie à sauvegarder
 *
 */
function saveGame($game){
	//Ajout de la date de mise à jour :
	$game['update_date'] = date(DATE_ATOMZ);
	store(DIR_GAME . $game['id'], $game);
}

/*
 * Charge une partie
 *
 * @param $gameId : la partie à charger
 *
 * @return $game
 *
 */
function loadGame($gameId){
	return unstore(DIR_GAME . $gameId);
}

/*
 * Enregistrement du joueur
 *
 * @param $player : le joueur à sauvegarder
 *
 */
function savePlayer($player){
	//Ajout de la date de mise à jour :
	$player['update_date'] = date(DATE_ATOMZ);
	store(DIR_PLAYER . $player['id'], $player);
	unlockPlayer($player['id']);
}

/*
 * Charge une partie
 *
 * @param $playerId : le joueur à charger
 *
 * @return $player
 *
 */
function loadPlayer($playerId){
	if(!is_file(DIR_PLAYER . $playerId)){
		return null;
	}
	return unstore(DIR_PLAYER . $playerId);
}


/**
 * Récupère les id de toutes les parties
 *
 * @return $gameIds : tableau d'id
 */
function getGameIds(){
	$gameIds = array();
	if ($handle = opendir(DIR_GAME)) {
	    while (false !== ($file = readdir($handle))) {
	        if ($file != "." && $file != "..") {
	        	$gameIds[] = $file;
	        }
	    }
	    closedir($handle);
	}
	return $gameIds;
}

/**
 * Récupère les id de toutes les parties
 *
 * @return $gameIds : tableau d'id
 */
function getPlayerIds(){
	$playerIds = array();
	if ($handle = opendir(DIR_PLAYER)) {
	    while (false !== ($file = readdir($handle))) {
	        if ($file != "." && $file != "..") {
	        	$playerIds[] = $file;
	        }
	    }
	    closedir($handle);
	}
	return $playerIds;
}

/**
 * Bloque les données d'un joueur en vue d'une écriture dessus
 */
function lockPlayer($playerId){
	$i = 0;
	$handle = fopen(DIR_PLAYER . $playerId, "a+");
	return flock($handle, LOCK_EX);
}

/**
 * Débloque les données d'un joueur
 */
function unlockPlayer($playerId){
	$handle = fopen(DIR_PLAYER . $playerId, "rw");
	return flock($handle, LOCK_UN);
}


/**
 * Filtre les données à renvoyer au client
 */
function filterData($game){
	if(ENVIRONMENT == 'PROD'){
		unset($game["players"]);
		unset($game["playerTurn"]);
		unset($game["update_date"]);
		unset($game["winner"]);
		unset($game["id"]);
		unset($game["author"]);
		unset($game["original"]);
	}
	return $game;
}

/**
 * Met à jour le jeu en fonction du contexte
 */
function updateGame($game, $add, $cardCollection){
	gamelog(sprintf('[%s] Le joueur %s joue la carte %s sur la case %s.', $game['id'], $game["playerTurn"], $add['card'], $add['cell']));
				
	//Suppression de la carte dans le paquet du joueur
	$game['players'][$game["playerTurn"]]["deck"] = takeCard($game['players'][$game["playerTurn"]]["deck"], $add['card']);
	
	//Modification de l'état des cartes adjacentes
	$game = execCardBattle($add, $game, $cardCollection, $game['players'][$game["playerTurn"]]['color']);
	
	//Ajout de la carte sur le plateau
	$game["board"][$add['cell']] = array($add['card'], $game['players'][$game["playerTurn"]]['color']);

	//Détection de la fin de la partie
	$game = detectEndOfGame($game['id'], $game, $game['players'][$_SESSION['playerId']]['color']);
	
	//Fin de partie : Libération des cartes bloquées
	if($game['endOfGame'] != 0){
		//Récupération des cartes des joueurs qui ont perdu
		$game["playerTurn"] = null;
		if($game['endOfGame'] == 1){
			gamelog(sprintf('[%s] Fin de la partie, gagnant : %s.', $game['id'], $game['winner']));	
			$game['gain'] = getCardsToChoose($game);
		}elseif($game['endOfGame'] == 3){
			releasePlayerCards($game);
			gamelog(sprintf('[%s] Fin de la partie, égalité.', $game['id']));	
		}			
	}else{
		//Modification du joueur suivant
		$game = changeNextPlayer($game);
	
		//Id du joueur suivant
		$nextPlayer = $game["playerTurn"];			
		gamelog(sprintf('[%s] Tour de l\'équipe %s.', $game['id'], $nextPlayer));
	}

	return $game;
}
/*
 * Indique si la partie est terminée ou non
 * 
 * @return boolean 
 */
function isGameOver($game){
	return $game['endOfGame'] != 0;
}

/**
 * Récupère et filtre les informations utiles au client
 */
function displayGameToClient($game){
	//Indication au client si tour du joueur
	if($game['playerTurn'] == $_SESSION['playerId']){
		$game["player"] = true;
	}else{
		$game["player"] = false;
	}
		
	//Récupération du deck du joueur connecté pour affichage
	$game["deck"] = $game["players"][$_SESSION['playerId']]["deck"];
	
	//Suppression des élements à cacher au client
	$game = filterData($game);	
	
	return json_encode($game);
}

/*
 * Calcul de la meilleure combinaison du joueur en cours
 * 
 * @return $add : array('card' => '', 'cell' => '')
 */
function getBestAdd($game, $cardCollection){
	$deck = $game["players"][$game["playerTurn"]]["deck"];
	
	//Initialisation d'un id de case / et de carte
	$cardId = $caseId = null;
	
	//Boucle sur chaque carte disponible
	$points = $maxPointsMax = 0;
	foreach($deck as $cardKey){
		//Boucle sur chaque place inoccupée du plateau
		foreach($game["board"] as $caseKey => $case){
			$points = 0;
			if($case == 'default'){
			    $add = array("card" => $cardKey
				 		,"cell" => $caseKey
				);					
				//Simulation de la partie, le CPU calcule la meilleure place possible
				execCardBattle($add, $game, $cardCollection, $game['players'][$game["playerTurn"]]['color'], $points);
				if($points > 0 && $maxPointsMax < $points){
					$caseId = $caseKey;
					$cardId = $cardKey;
					$maxPointsMax = $points;
				}
				//Par défaut la carte à poser est la première du jeu sur la première case
				if($caseId === null){
					$caseId = $caseKey;
					$cardId = $cardKey;
				}
			}
		}
	}
	return array("card" => $cardId
		 		,"cell" => $caseId
				);	
}

/**
 * Vérifie les cartes que joue le joueur
 * 
 * @param $game : la partie concernée
 * @param array $add : ce que joue le joueur
 * @param string : le joueur qui veut jouer
 * @param $cardCollection : une collection de cartes
 * 
 * @throw Exception si problème
 */
function checkAdd($game, $add, $playerId, $cardCollection){
	//Vérification de la présence du joueur dans la partie
	$gameId = $game['id'];
	if(!isInGame($game, $playerId)){
		gamelog(sprintf('[%s] Le joueur %s n\'a pas le droit de jouer à cette partie.', $gameId, $playerId));
		throw new Exception(sprintf('[%s] Le joueur %s n\'a pas le droit de jouer à cette partie.', $gameId, $playerId), HTTP_BADREQUEST);
	}
	
	//Vérification du tour du joueur
	if($game["playerTurn"] != $playerId ){
		gamelog(sprintf('[%s] Le joueur %s n\'a pas le droit de jouer, c\'est au tour du joueur %s.', $gameId, $playerId, $game["player"]));
		throw new Exception(sprintf('[%s] Le joueur %s n\'a pas le droit de jouer, c\'est au tour du joueur %s.', $gameId, $playerId, $game["playerTurn"]), HTTP_BADREQUEST);
	}
	
	//Vérification de la carte dans la collection
	if(!key_exists($add['card'], $cardCollection)){
		gamelog(sprintf('[%s] Le joueur %s joue la carte %s qui n\'existe pas.', $gameId, $game["playerTurn"], $add['card']));
		throw new Exception('La carte n\'existe pas dans la collection du jeu.', HTTP_BADREQUEST);
	}
	
	if(!in_array($add['card'], $game['players'][$game["playerTurn"]]["deck"])){
		gamelog(sprintf('[%s] Le joueur %s joue la carte %s qu\'il ne possède pas.', $gameId, $game["playerTurn"], $add['card']));
		throw new Exception('La carte n\'existe pas dans la main du joueur.', HTTP_BADREQUEST);
	}
	
	if(!key_exists($add['cell'], $game["board"])){
		gamelog(sprintf('[%s] Le joueur %s joue la carte %s sur la case %s qui n\'existe pas.', $gameId, $game["playerTurn"], $add['card'], $add['cell']));
		throw new Exception('L\'emplacement n\'est pas disponible car il n\'existe pas.', HTTP_BADREQUEST);
	}
	
	if($game["board"][$add['cell']] != 'default' ){
		gamelog(sprintf('[%s] Le joueur %s joue la carte %s sur la case %s qui est déjà occupé par la carte %s.', $gameId, $game["playerTurn"], $add['card'], $add['cell'], $game["board"][$add['cell']][0]));
		throw new Exception('L\'emplacement n\'est pas disponible car il est déjà occupé.', HTTP_BADREQUEST);
	}
}

/**
 * Détermine si la partie est composé d'un seul joueur humain 
 * Renvoie true si oui, false sinon
 * 
 * @param $game : la partie
 * 
 * @return boolean
 * 
 */
function isFullCPU($game){
	$nbHumans=0;
	foreach($game['players'] as $playersInfos){
		if($playersInfos['type'] == 'human'){
			$nbHumans++;
		}
	}
	if($nbHumans == 1){
		return true;
	}	
	return false;
}


